#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/sendfile.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<string>
#include<vector>
#include<unordered_map>
#include<sstream>
#include<algorithm>
#include<cstdlib>

#include"util.hpp"
#include"log.hpp"

#define WWWROOT "wwwroot"
#define HOMEHTML "index.html"
#define EDITION "HTTP/1.0"

using namespace std;

static string getCode(int code){
    switch(code){
        case 200:
            return "OK";
        case 404:
            return "NOT_FROUNT";
        defaule:
            break;
    }
}

static string getSuffix(const string& suffix){
    static unordered_map<string,string> mp={
        {".html","text/html"},
        {".css","text/css"},
        {".is","application/javascript"},
        {".jpg","application/x-jpg"},
        {".xml","application/xml"}
    };
    auto it=mp.find(suffix);
    if(it!=mp.end()){
        return it->second;
    }
    else{
        return "text/html";
    }
}

class RequestHttp{
    public:
        string request_line;
        vector<string> request_head;
        string request_blank;
        string request_text;

        string method;
        string url;
        string edition;

        string path;
        string parameter;

        unordered_map<string,string> mp;

        int content_length;

        bool request_cgi;
        int request_code;

        string suffix;

        RequestHttp():request_cgi(false),request_code(200)
        {}
        ~RequestHttp(){}
};
class ResponseHttp{
    public:
        string response_line;
        vector<string> response_head;
        string response_blank;
        string response_text;

        size_t response_size;

        ResponseHttp():response_blank("\r\n")
        {}
        ~ResponseHttp(){}
};

class Entterminal{
    private:
        RequestHttp request;
        ResponseHttp response;
        int sock;
        int fd;
        bool stop;
    public:
        Entterminal(int _sock):sock(_sock),fd(-1),stop(false)
        {}
        ~Entterminal(){
            if(sock>=0)
                close(sock);
        }
    public:
        bool Recvreuqestline(){
            if(Util::getline(sock,request.request_line)>0){
                request.request_line.resize(request.request_line.size()-1);

                stringstream ss(request.request_line);
                ss>>request.method>>request.url>>request.edition;
            
                transform(request.method.begin(),request.method.end(),request.method.begin(),::toupper);

                LOG(INFO,request.request_line);
                LOG(INFO,request.method);
                LOG(INFO,request.url);
                LOG(INFO,request.edition);
            }
            else{
                stop=true;
            }
            return stop;
        }
        bool Recvrequesthead(){
            if(!stop){
                string ss;
                while(true){
                    ss.clear();
                    if(Util::getline(sock,ss)<=0){
                        stop=true;
                        return stop;
                    }
                    if(ss!="\n"){
                        ss.resize(ss.size()-1);
                        request.request_head.push_back(ss);
                    }
                    else if(ss=="\n")
                        break;
                }
                request.request_blank=ss;

                for(auto& e:request.request_head){
                    int pos=e.find(": ");
                    if(pos!=std::string::npos){
                        string k=e.substr(0,pos);
                        string v=e.substr(pos+2);
                        request.mp.insert({k,v});
                    }
                }
                for(auto& e:request.request_head){
                    LOG(INFO,e);
                }
            }
            return stop;
        }

        bool getcontantlength(){
            if(request.method=="POST"){
                auto it=request.mp.find("Content-Length");
                if(it!=request.mp.end()){
                    request.content_length=atoi(it->second.c_str());
                    return true;
                }
                return false;
            }
        }
        bool Recvrequesttext(){
            if(!stop){
                if(getcontantlength()){
                    int len=request.content_length;
                    while(len){
                        char ch;
                        ssize_t s=recv(sock,&ch,1,0);
                        if(s>0){
                            request.request_text.push_back(ch);
                        }   
                        else{
                            stop=true;
                            break;
                        }
                        len--;
                    }   
                    LOG(INFO,request.request_text);
                }
            }
            return stop;
        }
        void Buildrequest(){
            string temp;
            struct stat st;
            int pos;
            if(request.method!="GET" && request.method!="POST"){
                LOG(ERROR,"NOT_MOTHOD");
                request.request_code=404;
                goto END;
            }
            if(request.method=="GET"){
                size_t pos=request.url.find('?');
                if(pos!=std::string::npos){
                    request.path=request.url.substr(0,pos);
                    request.parameter=request.url.substr(pos+1);
                    request.request_cgi=true;
                }
                else{
                    request.path=request.url;
                }
            }
            else{
                request.path=request.url;
                request.request_cgi=true;
            }
            temp=WWWROOT;
            temp+=request.path;
            request.path=temp;
            if(request.path[request.path.size()-1]=='/'){
                request.path+=HOMEHTML;
            }
            if(stat(request.path.c_str(),&st)==0){
                if(S_ISDIR(st.st_mode)){
                    request.path+='/';
                    request.path+=HOMEHTML;
                    stat(request.path.c_str(),&st);
                }
                if(st.st_mode&S_IXUSR ||st.st_mode&S_IXGRP ||st.st_mode&S_IXOTH ){
                    request.request_cgi=true;
                }
                response.response_size=st.st_size;

                pos=request.path.rfind('.');
                if(pos!=std::string::npos){
                    request.suffix=request.path.substr(pos);
                }
                else{
                    request.suffix=".html";
                }
            }
            else{
                string s=request.path;
                s+="NOT_FROUNT";
                LOG(ERROR,s);
                request.request_code=404;
                goto END;
            }
            LOG(INFO,request.path);
            if(request.request_cgi){
                LOG(INFO,"cgi...");
                request.request_code=Processcgi();
            }
            else{
                request.request_code=Processnotcgi();   
            }
END:
            Buildresponse();
            return;
        }
        void Buildresponse(){
            auto code=request.request_code;

            response.response_line+=EDITION;
            response.response_line+=" ";
            response.response_line+=to_string(request.request_code);
            response.response_line+=" ";
            response.response_line+=getCode(request.request_code);
            response.response_line+="\r\n";

            string _path;
            _path=WWWROOT;
            _path+="/";
            switch(code){
                case 200:
                    Build200();
                    break;
                case 404:
                    _path+="404.html";
                    LOG(INFO,"执行404...");
                    Build404(_path);
                    break;
                defaule:
                    break;
            }
        }

        void Build200(){
            string suffix="Content-Type:";
            suffix+=" ";
            suffix+=getSuffix(request.suffix);
            suffix+="\r\n";
            response.response_head.push_back(suffix);

            string content_length="Content-Length:";
            content_length+=" ";
            if(request.request_cgi){
                content_length+=to_string(response.response_text.size());
            }
            else{
                content_length+=to_string(response.response_size);
            }
            content_length+="\r\n";
            response.response_head.push_back(content_length);
        }

        void Build404(string& temppath){
            request.request_cgi=false;
            LOG(INFO,temppath);
            fd=open(temppath.c_str(),O_RDONLY);
            if(fd>=0){
                LOG(INFO,"进入了404,并打开了文件...");
                string suffix="Content-Type: text/html";
                suffix+="\r\n";
                response.response_head.push_back(suffix);


                string content_length="Content-Length:";
                content_length+=" ";
                struct stat st;
                stat(temppath.c_str(),&st);
                response.response_size=st.st_size;
                content_length+=to_string(st.st_size);
                content_length="\r\n";
                response.response_head.push_back(content_length);
            }
        }

        int Processcgi(){
            int code=200;

            string& path=request.path;
            string& method=request.method;
            string parameter_env;
            string method_env;
            string length_env;
            int input[2];
            int output[2];
            if(pipe(input)<0){
                code=404;
                return code;
            }
            if(pipe(output)<0){
                code=404;
                return code;
            }
            pid_t id=fork();
            if(id==0){
                close(input[0]);//input[1]写
                close(output[1]);//output[0]读

                dup2(input[1],1);
                dup2(output[0],0);

                method_env="METHOD=";
                method_env+=method;
                putenv((char*)method_env.c_str());

                if(method=="GET"){
                    parameter_env="PARAMETER=";
                    parameter_env+=request.parameter;
                    putenv((char*)parameter_env.c_str());
                }
                if(method=="POST"){
                    length_env="CONTENT_LENGTH=";
                    length_env+=to_string(request.content_length);
                    putenv((char*)length_env.c_str());
                }
                execl(path.c_str(),path.c_str(),nullptr);
                exit(1);
            }
            
                close(input[1]);//intput[0]读
                close(output[0]);//output[1]写
                if(method=="POST"){
                    const char* str=request.request_text.c_str();
                    int start=0;
                    int size=0;
                    while((size=write(output[1],str+start,request.request_text.size()-start))>0){
                        start+=size;
                    }
                }
                char ch=0;
                while(read(input[0],&ch,1)>0){
                    response.response_text.push_back(ch);
                }
                int status=0;
                pid_t ret=waitpid(id,&status,0);
                if(ret==id){
                    if(WIFEXITED(status)){
                        if(WEXITSTATUS(status)==0){
                            code=200;
                        }
                        else{
                            code=404;
                        }
                    }
                    else{
                        code=404;
                    }
                }
                close(input[0]);
                close(output[1]);
            
            return code;
        }
        int Processnotcgi(){
            fd=open(request.path.c_str(),O_RDONLY);
            if(fd>=0){
                return 200;
            }
            return 404;
        }
        void SendResponse(){
            send(sock,response.response_line.c_str(),response.response_line.size(),0);
            for(auto& e:response.response_head){
                send(sock,e.c_str(),e.size(),0);
            }
            send(sock,response.response_blank.c_str(),response.response_blank.size(),0);
            if(request.request_cgi){
                int table=0;
                int size=0;
                const char* str=response.response_text.c_str();
                while((size=send(sock,str+table,response.response_text.size()-table,0))>0){
                    table+=size;
                }
            }
            else{
                LOG(INFO,"发送到了sock中...");
                sendfile(sock,fd,nullptr,response.response_size);
                close(fd);
            }
        }
        bool IsStop(){
            return stop;
        }   
};


class CallBack{
    public:
        CallBack(){}
        ~CallBack(){}
        void operator()(int sock){
            Handle(sock);
        }
        void Handle(int sock){

            Entterminal* en=new Entterminal(sock);
            LOG(INFO,"...........begin.........");
            en->Recvreuqestline();
            en->Recvrequesthead();
            en->Recvrequesttext();
            if(!(en->IsStop())){
                LOG(INFO,"succsee...yes buil and send");
                en->Buildrequest();
                en->SendResponse();
            }
            else{
                LOG(WARNING,"error...no buil and send");
            }  
            LOG(INFO,".........end.............");
            delete en;
        }
};
